// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#pragma once

#include <map>
#include <memory>
#include <string>
#include <tuple>

#include "coin2/exchange/base/market/enums.h"
#include "coin2/exchange/base/symbology/product_holder_manager.h"

// cache store for Product and ProductHolder
// try to have only 1 instance of this class
namespace coin2::exchange::base::symbology {
namespace impl {

using Key = std::tuple<
    coin2::exchange::base::market::MarketType,
    coin2::exchange::base::market::ExchangeType,
    std::string>;
class ProductEncyclopedia {
 public:
  ProductEncyclopedia() = default;
  const IProductHolder& holder(const IProduct& product) {
    auto key = std::make_tuple(product.market(), product.exchange(), product.absolute_norm());
    if (holder_map_.find(key) == holder_map_.end()) {
      if (product.market() == MarketType::Options && product.exchange() == ExchangeType::Deribit &&
          after_delivery(product.resolve_timestamp())) {
        return *(
          holder_map_[key] = std::unique_ptr<IProductHolder>(
              GetProductHolderManager2()
                  ->GetProductHolder(product, product.resolve_timestamp())
                  ->Clone()));
      }
      return *(
          holder_map_[key] = std::unique_ptr<IProductHolder>(
              GetProductHolderManager()
                  ->GetProductHolder(product, product.resolve_timestamp())
                  ->Clone()));
    } else {
      return *holder_map_.at(key);
    }
  }
  const IProductHolder& holder_at(const IProduct& product) {
    auto key = std::make_tuple(product.market(), product.exchange(), product.absolute_norm());
    return *holder_map_.at(key);
  }

  using ProductHolderManager = coin2::exchange::base::symbology::ProductHolderManager;
  static ProductHolderManager* GetProductHolderManager() {
    static std::unique_ptr<ProductHolderManager> phm(
        new ProductHolderManager(coin::proto::ProductHolderManagerConfig()));
    return phm.get();
  }

  // Single instance ProductHolderManager will have problems getting the pi on the delivery date
  // ProductHolderManager created before the delivery time will contain only the product before the delivery date.
  // Use ProductHolderManager2 to create product universe again after the UTC8 to solve this problem.
  static ProductHolderManager* GetProductHolderManager2() {
    static std::unique_ptr<ProductHolderManager> phm2(
        new ProductHolderManager(coin::proto::ProductHolderManagerConfig()));
    return phm2.get();
  }

 private:
  std::map<Key, std::unique_ptr<IProductHolder>> holder_map_;

  bool after_delivery(int64_t ts) {
    int64_t day = 3600 * 24 * 1'000'000'000LL;
    int64_t uct0ts = (ts / day) * day;
    if (ts - uct0ts > 3600 * 8 * 1'000'000'000LL) { return true; }
    return false;
  };
};
}  // namespace impl
using impl::ProductEncyclopedia;
}  // namespace coin2::exchange::base::symbology
